#!/usr/bin/env python


# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

"""
Adapted from Mike Miller's wxWizard page on wxPyWiki.
"""

import os.path, socket, threading, wx, wx.wizard
from wx.lib.newevent import *
from Dingo.calibrate.calibrator import *

padding = 5

__author__ = 'Sarah Mount <s.mount@wlv.ac.uk>'
__credits__ = 'Mike Miller'


# Globals
DEBUG = False

calibrator = Calibrator() # From the calibrator module. Should be a singleton

# Custom events
PageChange, EVT_PAGE_CHANGE = NewEvent()
RcvData,    EVT_RCV_DATA    = NewEvent()

# Sockets (Will need to be moved)
localhost = socket.gethostname()	# Localhost
port = 8081				# Port number for sockets
buffsize = 1024				# Buffer size for sockets

# Getting sensor data
din = False				# Set if we're receiving data
blank_data = [(0.0, 0.0)]		# Also used by OnClear()
data = blank_data[:] 			# Global to store plot data
data_l = threading.Lock()		# Lock for the data variable

def socketlistener(wizard):
	"""Listen on a UDP socket for sensor data."""
	sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	sock.bind(('', port))
        global din
	while True:
    		data, addr = sock.recvfrom(buffsize)
                if accel and din:
                        points = PlottingApp.unmarshaller.unmarshall(accel)
                        if not points:
                            continue
                        data_l.acquire()
			data.append(points)
                        data_l.release()
                        evt = RcvData(data)
                        wx.PostEvent(plotter, evt)
    	return


def opj(path):
    """Convert paths to the platform-specific separator.
    From wxPython demos.
    """
    str = apply(os.path.join, tuple(path.split('/')))
    # HACK: on Linux, a leading / gets lost...
    if path.startswith('/'):
        str = '/' + str
    return str


class WizardPage(wx.wizard.PyWizardPage):
    def __init__(self, parent, title):
        wx.wizard.PyWizardPage.__init__(self, parent)
        self.next = self.prev = None
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        title = wx.StaticText(self, -1, title)
        title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
        self.sizer.AddWindow(title, 0, wx.ALIGN_LEFT|wx.ALL, padding)
        self.sizer.AddWindow(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, padding)
	self.SetAutoLayout(True)
        self.SetSizer(self.sizer)
        self.SetForegroundColour(wx.Colour(0,0,0))
#        self.SetBackgroundColour(wx.Colour(255, 255, 255))
    	self.Bind(EVT_PAGE_CHANGE, self.OnPageChange)
        EVT_PAGE_CHANGE(self, self.OnPageChange)
                       
    def OnPageChange(self, evt):
        if DEBUG: print 'Custom event handler called'
        return

    def add_stuff(self, stuff):
        'Add aditional widgets to the bottom of the page'
        success = self.sizer.Add(stuff, 0, wx.GROW|wx.ALL, padding)
        self.sizer.SetSizeHints(self)
        return success

    def SetNext(self, next):
        'Set the next page'
        self.next = next

    def SetPrev(self, prev):
        'Set the previous page'
        self.prev = prev

    def GetNext(self):
        'Return the next page'
        return self.next

    def GetPrev(self):
        'Return the previous page'
        return self.prev


### Custom pages for calibrate and test

class SelectDevicePage(WizardPage):
    def __init__(self, parent, title):
        WizardPage.__init__(self, parent, title)
	self.add_stuff(wx.StaticText(self, -1, 'Select a device to calibrate:'))
	cb = wx.ComboBox(self, 500, 'Select device', (120, 50), (125, -1),
                         hardware.keys(), wx.CB_DROPDOWN)
        self.add_stuff(cb)
        self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, cb)
        cb.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        cb.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
        cb.SetFocus()
	self.description = None
        self.image = None

    def OnSetFocus(self, evt):
        return
    def OnKillFocus(self, evt):
        return
    
    def EvtComboBox(self, evt):
        if DEBUG: print 'Combo box event'
        cb = evt.GetEventObject()
        data = cb.GetValue()
        # Remove description text
        if self.description != None:
            self.description.Destroy()
            self.sizer.Layout() # Refresh the sizer on screen
        if self.image != None:
            self.image.Destroy()
            self.sizer.Layout() # Refresh the sizer on screen
        self.description = wx.StaticText(self, -1, hardware[data][1])
####### BROKEN WITH TypeError: String or Unicode type required
        self.image = wx.StaticBitmap(self, -1,
                                     bitmap=wx.Bitmap(hardware[data][0], wx.BITMAP_TYPE_PNG)
                                     )
        if self.image != None: self.add_stuff(self.image)
        self.add_stuff(self.description)
        calibrator.setDevice(data)


class SelectSensorPage(WizardPage):
    def __init__(self, parent, title):
        WizardPage.__init__(self, parent, title)
	# Combo box
        self.cb = None
        self.t1 = None
    def OnPageChange(self, evt):
        if self.t1 != None:
            self.t1.Destroy()
            self.sizer.Layout()
        if self.cb != None:
            self.cb.Destroy()
            self.sizer.Layout()
        if calibrator.device == None or not (calibrator.device in hardware):
            dlg = wx.MessageDialog(self, ('You have not selected a device to calibrate.\n' +
                                          'Please go back a page.'),
                                   'Missing device name error',
                                   wx.OK | wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()
        elif calibrator.device != None:
            t = ('Calibration device is a: ' + calibrator.device +
                           '. Select a sensor to calibrate:')
            self.t1 = wx.StaticText(self, -1, t)
            self.add_stuff(self.t1)
            cb = wx.ComboBox(self, 501, value='Select sensor', size=(200, -1), 
                             pos=(5, 90), choices=[], style=wx.CB_DROPDOWN)
            cb.Show()
	    cb.SetFocus()
            for sensor in hardware[calibrator.device][2]:
            	cb.Append(sensor)
            self.cb = cb
            self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.cb)
        else:
            dlg = wx.MessageDialog(self, 'Something has gone wrong...\nPlease try again.',
                                   'Cannot select sensor.',
                               wx.OK | wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()

    def EvtComboBox(self, evt):
        cb = evt.GetEventObject()
        if DEBUG: print 'Combo box event'
        data = cb.GetValue()
        if DEBUG: print 'data:', data
        calibrator.sensor = data
        if DEBUG: print 'calibrating', calibrator.device, 'with', calibrator.sensor, 'sensor'
        return


class CalculateOffsetsPage(WizardPage):
	def __init__(self, parent, title):
		WizardPage.__init__(self, parent, title)
		return


class LineariseOutputPage(WizardPage):
	def __init__(self, parent, title):
		WizardPage.__init__(self, parent, title)
		return


class Wizard(wx.wizard.Wizard):
    'Add pages to this wizard object to make it useful.'
    def __init__(self, title, img_filename=""):
        # img could be replaced by a py string of bytes
        if img_filename and os.path.exists(img_filename):
                img = wx.Bitmap(img_filename)
        else:   img = wx.NullBitmap
        wx.wizard.Wizard.__init__(self, None, -1, title, img)
        self.pages = []
        self.SetPageSize(wx.Size(500, 550))
        self.CenterOnScreen()
        self.EnableCloseButton(True)
        self.MakeModal()
        self.Raise()

        self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.on_page_changed)
        self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.on_page_changing)
        self.Bind(wx.wizard.EVT_WIZARD_CANCEL, self.on_cancel)
        self.Bind(wx.wizard.EVT_WIZARD_FINISHED, self.on_finished)

    def add_page(self, page):
        'Add a wizard page to the list.'
        if self.pages:
            previous_page = self.pages[-1]
            page.SetPrev(previous_page)
            previous_page.SetNext(page)
        self.pages.append(page)

    def run(self):
        self.RunWizard(self.pages[0])

    def on_page_changed(self, evt):
        'Executed after the page has changed.'
        if evt.GetDirection():
            dir = "forward"
        else:
            dir = "backward"
        page = evt.GetPage()
        if DEBUG: print "page_changed: %s, %s\n" % (dir, page.__class__)

    def on_page_changing(self, evt):
        'Executed before the page changes, so we might veto it.'
        if evt.GetDirection():
            dir = "forward"
        else:
            dir = "backward"
    	page = evt.GetPage()
        for page in self.pages:
        	wx.PostEvent(page, PageChange())
        if DEBUG: print "page_changing: %s, %s\n" % (dir, page.__class__)

    def on_cancel(self, evt):
        'Cancel button has been pressed.  Clean up and exit without continuing.'
        page = evt.GetPage()
        if DEBUG: print "on_cancel: %s\n" % page.__class__

        # Prevent cancelling of the wizard.
        #if page is self.pages[0]:
        #    wx.MessageBox("Cancelling on the first page has been prevented.", "Sorry")
        #    evt.Veto()

    def on_finished(self, evt):
        'Finish button has been pressed.  Clean up and exit.'
        if DEBUG: print "OnWizFinished\n"


if __name__ == '__main__':

    app = wx.PySimpleApp()

    mywiz = Wizard('Dingo calibrate and correct wizard', img_filename='wiz.png')
    page1 = SelectDevicePage(mywiz, 'Device type')
    page2 = SelectSensorPage(mywiz, 'Sensor type')
    page3 = CalculateOffsetsPage(mywiz, 'Calculate offset')
    page4 = LineariseOutputPage(mywiz, 'Linearize output')
    
    # Add some more pages
    mywiz.add_page(page1)
    mywiz.add_page(page2)
    mywiz.add_page(page3)
    mywiz.add_page(page4)

    mywiz.run() # Show the main window

    # Cleanup
    mywiz.Destroy()
    app.MainLoop()
